[!NOTE]
This is one of 189 standalone projects, maintained as part
of the @thi.ng/umbrella monorepo
and anti-framework.
🚀 Please help me to work full-time on these projects by sponsoring me on
GitHub. Thank you! ❤️
About
Alternative Map and Set implementations with customizable equality semantics & supporting operations, plain object utilities.
- Array based
ArraySet
, Linked List based LLSet
,
Skiplist based SortedMap
&
SortedSet
and customizable EquivMap
implement the full ES6 Map/Set APIs
and additional features:
- range query iterators (via
entries()
, keys()
, values()
) (sorted
types only) ICopy
, IEmpty
& IEquiv
implementationsICompare
implementation for sorted types- multiple value additions / updates / deletions via
into()
, dissoc()
(maps) and disj()
(sets) - configurable key equality & comparison (incl. default implementations)
- getters w/ optional "not-found" default value
fromObject()
converters (for maps only)
TrieMap
for string-based keys and MultiTrie
for array-like keys and
multiple values per keySparseSet
implementations for numeric values- Polymorphic set operations (union, intersection, difference) - works with both
native and custom Sets and retains their types
- Natural & selective
joins
(incl. key renaming, ported from Clojure)
- Key-value pair inversion for maps and vanilla objects
- i.e. swaps
K => V
to V => K
- Single or multi-property index generation for maps and objects
- Key selection, renaming, segmenting, splitting, transformations for maps and
objects
Why?
Please see these packages for some example use cases:
The native ES6 implementations use object reference identity to
determine key containment, but often it's more practical and useful to
use equivalent value semantics for this purpose, especially when keys
are structured data (arrays / objects).
Note: It's the user's responsibility to ensure the inserted keys are
kept immutable (even if technically they're not).
Comparison with ES6 native types
a = [1, 2];
b = [1, 2];
Using native implementations
set = new Set();
set.add(a);
set.has(b);
map = new Map();
map.set(a, "foo");
map.get(b);
Using custom implementations:
import { defArraySet } from "@thi.ng/associative";
set = defArraySet();
set.add(a);
set.add({a: 1});
set.has(b);
set.has({a: 1});
import { defLLSet } from "@thi.ng/associative";
set = defLLSet();
set.add(a);
set.add({a: 1});
set.has(b);
set.has({a: 1});
import { defEquivMap } from "@thi.ng/associative";
map = defEquivMap();
map = defEquivMap(null, { keys: assoc.ArraySet });
map.set(a, "foo");
map.get(b);
import { defHashMap } from "@thi.ng/associative";
import { hash } from "@thi.ng/vectors"
m = defHashMap([], { hash })
m.set([1, 2], "a");
m.set([3, 4, 5], "b");
m.set([1, 2], "c");
import { defSortedSet, defSortedMap } from "@thi.ng/associative";
set = defSortedSet([a, [-1, 2], [-1, -2]]);
set.has(b);
map = defSortedMap([[a, "foo"], [[-1,-2], "bar"]]);
map.get(b);
map.get([3,4], "n/a");
Status
STABLE - used in production
Search or submit any issues for this package
Installation
yarn add @thi.ng/associative
ESM import:
import * as assoc from "@thi.ng/associative";
Browser ESM import:
<script type="module" src="https://esm.run/@thi.ng/associative"></script>
JSDelivr documentation
For Node.js REPL:
const assoc = await import("@thi.ng/associative");
Package sizes (brotli'd, pre-treeshake): ESM: 6.98 KB
Dependencies
Usage examples
Four projects in this repo's
/examples
directory are using this package:
Screenshot | Description | Live demo | Source |
---|
| Heatmap visualization of this mono-repo's commits | | Source |
| Augmenting thi.ng/geom shapes for WebGL, using instancing & attribute buffers | Demo | Source |
| rstream & transducer-based FSM for converting key event sequences into high-level commands | Demo | Source |
| Responsive image gallery with tag-based Jaccard similarity ranking | Demo | Source |
API
Generated API docs
IEquivSet
All Set
implementations in this package implement the
IEquivSet
interface, an extension of the native ES6 Set API.
ArraySet
Simple array based Set
implementation which by default uses
@thi.ng/equiv
for value equivalence checking.
LLSet
Similar to ArraySet
, but uses
@thi.ng/dcons linked list
as backing storage for values.
EquivMap
This Map
implementation uses a native ES6 Map
as backing storage for
its key-value pairs and an additional IEquivSet
implementation for
canonical keys. By default uses ArraySet
for this purpose.
HashMap
Map implementation w/ standard ES6 Map API, supporting any key type via
hash codes computed via user supplied hash function. Uses Open
Addressing / Linear
Probing to resolve key collisions. Customizable via HashMapOpts
constructor argument. Hash function MUST be given.
SortedMap
Alternative implementation of the ES6 Map API using a Skip list as
backing store and support for configurable key equality and sorting
semantics. Like with sets, uses @thi.ng/equiv & @thi.ng/compare by
default.
William Pugh's (creator of this data structure) description:
"Skip lists are probabilistic data structures that have the same
asymptotic expected time bounds as balanced trees, are simpler, faster
and use less space."
Data structure description:
Ranged queries
import { defSortedMap } from "@thi.ng/associative";
map = defSortedMap([
["c", 3], ["a", 1], ["d", 4], ["b", 2]
]);
[...map.entries()]
[...map.entries("c")]
[...map.entries("cc")]
[...map.entries("c", true)]
SortedSet
Sorted set implementation with standard ES6 Set API, customizable value
equality and comparison semantics and additional functionality:
- range queries (via
entries
, keys
, values
) - multiple value addition/deletion via
into()
and disj()
Furthermore, this class implements the ICopy
, IEmpty
, ICompare
and
IEquiv
interfaces defined by @thi.ng/api
. The latter two allow
instances to be used as keys themselves in other data types defined in
this (and other) package(s).
This set uses a SortedMap
as backing store.
SparseSet8/16/32
Sparse sets provide super fast
(approx. 4x faster than the native Set
impl) insertion & lookups for
numeric values in the interval [0..n)
. The implementation in this
package provides most of the ES6 Set API and internally relies on 2 uint
typed arrays, with the actual backing type dependent on n
.
Furthermore, unless (or until) values are being removed from the set,
they retain their original insertion order. For some use cases (e.g.
deduplication of values), this property can be very useful.
import { defSparseSet } from "@thi.ng/associative";
const a = defSparseSet(100);
a.into([99, 42, 66, 23, 66, 42]);
a.has(66)
[...a]
a.add(100)
const b = defSparseSet(0x10000);
TrieMap
Tries (also called Prefix maps) are useful
data structures for search based use cases, auto-complete, text indexing etc.
and provide partial key matching (prefixes), suffix iteration for a common
prefix, longest matching prefix queries etc.
The implementations here too feature ES6 Map-like API, similar to other types in
this package, with some further trie-specific additions.
import { defTrieMap } from "@thi.ng/associative";
const trie = defTrieMap([
["hey", "en"],
["hello", "en"],
["hallo", "de"],
["hallo", "de-at"],
["hola", "es"],
["hold", "en"],
["hej", "se"],
]);
trie.knownPrefix("hole")
[...trie.suffixes("he")]
[...trie.suffixes("he", true)]
MultiTrie
The MultiTrie
is similar to TrieMap
, but supports array-like keys and
multiple values per key. Values are stored in sets whose implementation can be
configured via ctor options.
import { defMultiTrie } from "@thi.ng/associative";
const t = defMultiTrie<string[], string>(null, { vals: () => new ArraySet() });
t.add("to be or not to be".split(" "), 1);
t.add("to be or not to be".split(" "), 2);
t.add("to be and to live".split(" "), 3);
t.get("to be or not to be".split(" "))
t.knownPrefix(["to", "be", "not"]);
[...t.suffixes(["to", "be"], false, "/")]
Authors
If this project contributes to an academic publication, please cite it as:
@misc{thing-associative,
title = "@thi.ng/associative",
author = "Karsten Schmidt",
note = "https://thi.ng/associative",
year = 2017
}
License
© 2017 - 2024 Karsten Schmidt // Apache License 2.0